package cc.yaomm.common.basic.base;

import cc.yaomm.common.basic.domain.Response;
import cc.yaomm.common.basic.enums.ResponseCode;
import cc.yaomm.common.basic.exception.BusinessException;
import cc.yaomm.common.basic.exception.ServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>@Description : 默认异常处理类</p>
 * <p>@Author : QiLin.Xing </p>
 * <p>@Date : 2018/8/2 17:18 </p>
 */
@ControllerAdvice
public class SuperExceptionHandler {

    private Logger logger = LoggerFactory.getLogger(SuperExceptionHandler.class);

    /**
     * <p>@Description : 将数据绑定到@RequestMapping注解，在执行方法之前初始化数据绑定器</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2018/8/2 17:21 </p>
     */
    @InitBinder
    public void initBinder(WebDataBinder binder) {
    }

    /**
     * <p>@Description : 将值绑定到model中，使全局@RequestMapping可以获取到值</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2018/8/2 17:23 </p>
     * <p>
     * 设置值
     * model.addAttribute("author", "Magical Sam");
     * <p>
     * 获取值
     *
     * @RequestMapping("/home") public String home(ModelMap modelMap)
     * System.out.println(modelMap.get("author"));
     */
    @ModelAttribute
    public void addAttributes(Model model) {
    }

    /**
     * <p>@Description : 客户端引起的异常 </p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2021/10/27 17:05 </p>
     */
    @ResponseBody
    @ExceptionHandler(value = {
            HttpRequestMethodNotSupportedException.class,
            HttpMediaTypeNotSupportedException.class,
            HttpMediaTypeNotAcceptableException.class,
            NoHandlerFoundException.class,})
    public Response notSupportedException(Exception ex, HttpServletResponse response, HttpHeaders headers) {
        String message = ex.getLocalizedMessage();
        if (ex instanceof HttpRequestMethodNotSupportedException) {
            message = "不支持此请求方式";
            response.setStatus(HttpStatus.METHOD_NOT_ALLOWED.value());
            Set<HttpMethod> supportedMethods = ((HttpRequestMethodNotSupportedException) ex).getSupportedHttpMethods();
            if (!CollectionUtils.isEmpty(supportedMethods)) {
                headers.setAllow(supportedMethods);
            }
            logger.error(String.join(":", message, ex.getMessage()));
        } else if (ex instanceof HttpMediaTypeNotSupportedException) {
            message = "不支持此内容类型";
            response.setStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value());
            List<MediaType> mediaTypes = ((HttpMediaTypeNotSupportedException) ex).getSupportedMediaTypes();
            if (!CollectionUtils.isEmpty(mediaTypes)) {
                headers.setAccept(mediaTypes);
            }
            logger.error(String.join(":", message, ex.getMessage()));
        } else if (ex instanceof HttpMediaTypeNotAcceptableException) {
            message = "无法接收此内容类型";
            response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
            logger.error(String.join(":", message, ex.getMessage()), ex);
        } else if (ex instanceof NoHandlerFoundException) {
            message = "请求接口不存在";
            response.setStatus(HttpStatus.NOT_FOUND.value());
            logger.error(String.join(":", message, ex.getMessage()));
        }
        return Response.fail(ResponseCode.FAILED, message, ex.getMessage());
    }

    /**
     * <p>@Description : 验证参数异常 </p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2021/10/27 16:56 </p>
     */
    @ResponseBody
    @ExceptionHandler(value = {
            BindException.class,
            MethodArgumentNotValidException.class,
            ConstraintViolationException.class,
            ValidationException.class,
            IllegalArgumentException.class
    })
    public Response validParamsException(Exception ex, HttpServletResponse response) {
        response.setStatus(HttpStatus.BAD_REQUEST.value());
        String message = ex.getLocalizedMessage();
        if (ex instanceof BindException) {
            ObjectError objectError = ((BindException) ex).getBindingResult().getAllErrors().get(0);
            message = Objects.nonNull(objectError) ? objectError.getDefaultMessage() : ex.getMessage();
        } else if (ex instanceof MethodArgumentNotValidException) {
            ObjectError objectError = ((MethodArgumentNotValidException) ex).getBindingResult().getAllErrors().get(0);
            message = Objects.nonNull(objectError) ? objectError.getDefaultMessage() : ex.getMessage();
        } else if (ex instanceof ConstraintViolationException) {
            Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException) ex).getConstraintViolations();
            message = constraintViolations.stream()
                    .map(x -> x.getMessage())
                    .distinct().collect(Collectors.joining(";"));
        } else if (ex instanceof ValidationException || ex instanceof IllegalArgumentException) {
            message = ex.getLocalizedMessage();
        }

        logger.error(String.join(":", message, ex.getMessage()), ex);
        return Response.fail(ResponseCode.FAILED, message, ex.getMessage());
    }

    /**
     * <p>@Description : 解析参数异常 </p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2021/10/27 16:57 </p>
     */
    @ResponseBody
    @ExceptionHandler(value = {
            MissingPathVariableException.class,
            MissingServletRequestParameterException.class,
            HttpMessageNotReadableException.class
    })
    public Response parseParamsException(Exception ex, HttpServletResponse response) {
        response.setStatus(HttpStatus.BAD_REQUEST.value());
        logger.error("解析参数异常:{}", ex.getMessage(), ex);
        return Response.fail(ResponseCode.FAILED, "请求参数错误", ex.getMessage());
    }

    /**
     * <p>@Description : 捕获已知异常 </p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2021/10/27 17:05 </p>
     */
    @ResponseBody
    @ExceptionHandler(value = {
            ServletRequestBindingException.class,
            ConversionNotSupportedException.class,
            TypeMismatchException.class,
            HttpMessageNotWritableException.class,
            MissingServletRequestPartException.class,
            AsyncRequestTimeoutException.class
    })
    public Response knownException(Exception ex, HttpServletResponse response) {
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        logger.error("系统异常:{}", ex.getMessage(), ex);
        return Response.fail(ResponseCode.EXCEPTION, "系统异常，请稍候重试", ex.getMessage());
    }

    /**
     * <p>@Description : 自定义业务异常</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2018/8/2 17:25 </p>
     */
    @ResponseBody
    @ExceptionHandler(value = {
            BusinessException.class,
            ServiceException.class})
    public Response errorException(Exception ex, HttpServletResponse response) {
        response.setStatus(HttpStatus.BAD_REQUEST.value());
        if (ex instanceof BusinessException) {
            SuperEnum respCode = ((BusinessException) ex).getCode();
            if (Objects.isNull(respCode)) {
                logger.error("业务层异常:{}", ex.getMessage(), ex);
                respCode = ResponseCode.FAILED;
            } else {
                logger.error("业务层异常:{}", ex.getMessage());
            }
            respCode = Objects.isNull(respCode) ? ResponseCode.FAILED : respCode;
            return Response.fail(respCode, ex.getLocalizedMessage(), ex.getMessage());
        } else {
            logger.error("服务层异常:{}", ex.getMessage(), ex);
            return Response.fail(ResponseCode.EXCEPTION, ex.getLocalizedMessage(), ex.getMessage());
        }
    }

    /**
     * <p>@Description : 捕获未知异常 </p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2021/10/27 17:05 </p>
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public Response unknownException(Exception ex, HttpServletResponse response) {
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        logger.error("全局异常:{}", ex.getMessage(), ex);
        return Response.fail(ResponseCode.EXCEPTION, "系统异常，请稍候重试", ex.getMessage());
    }
}
